(defun peer-server(port)
	(init-variables)
	(let
		((server (socket:socket-server port)))
		(format t "---- Waiting for a connection on ~S:~D~%"
			(socket:socket-server-host server) 
			(socket:socket-server-port server)
		)
		(unwind-protect
			;;;; infinite loop
			(loop 
				(with-open-stream (socket (socket:socket-accept server))
					(format t "---- Begin request~%")
					(multiple-value-bind (local-host local-port) 
						(socket:socket-stream-local socket)
						(multiple-value-bind (remote-host remote-port) 
							(socket:socket-stream-peer socket)
							(format t "---- Remote: ~S:~D <--> Local: ~S:~D~%"
								remote-host remote-port local-host local-port
							)
							(process-request socket
								(read-request socket) remote-host)
							)
							
					)
					(format t "---- End request~%")
				)
			)
			(socket:socket-server-close server)
			(save-server-info)
			(format t "---- Server shutted down~%")
		)
	)
)



(defun read-request (socket)
"Reads command from socket stream"
	(read-line socket nil nil)
)

(defun process-request (socket command peer)
"Processes request"
	(format t "---- Command: ~S~%" command)
	(cond
		((string-equal command "C") (process-connect socket))
		((string-equal command "D") (process-disconnect socket))
		((string-equal command "S") (process-search socket))
		((string-equal command "G") (process-get-file socket))
		((string-equal command "V") (process-verify socket))
	)
)

(defun process-connect(socket)
"Handles connect command"
	(let ( (s-data (read socket)) (uid (generate-id)))
		(princ s-data)
		(format t "---- Connect~%")
		(add-client uid (car s-data))
		(add-files (car (cdr s-data)) uid)
		(save-server-info)
		(format socket "~S" uid)
	)
)

(defun process-disconnect(socket)
"Handles disconnect command"
	(format t "---- Disconnect~%")
	(let ( (uid (getf (read socket) :id)))
		(princ uid)
		(format t "---- Disconnected client id - ~S~%" uid)
		(delete-client uid)
		(save-server-info)
		;;(format socket "~S" 'OK)
	)
)

(defun process-search(socket)
	(format t "---- Search file~%")
	(let ( (s-query (mapcar #'string (read socket))))
		(princ s-query)
		(format t "---- Query~S~%" s-query)
		(print (search-files s-query) socket)
	)
)

(defun process-get-file(socket)
"Handles get file location command"
	(format t "---- Get file~%")
	(let ( (info (read socket)))
		(princ info)
		(get-client-by-hash (getf info :hash) (getf info :id))
		(format socket "~S" (get-next-client (getf info :id)))
	)
)

(defun process-verify(socket)
"Handles verify command"
	(format t "---- Verify~%")
	(let ( (info (read socket)))
		(princ info)
		(format socket "~S" (verify (getf info :hash) (getf info :id)))
	)
	
)

(defun verify(file-hash client-id)
"Verifies download"
	(let 
		((download (car (remove-if-not #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))))
		(cond
			(
				(equal (getf download :id) client-id)
				(setf *downloads* (remove-if #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))
				'Y
			)
			(t 'N)
		)
	)	
)

(defun generate-id()
"Generates new id foe client"
	(funcall #'(lambda () (setf count (1+ count))))
)

(defun add-client (id client) 
"Adds new client to clients list"
	(push (cons :id (cons id client)) *clients*)
)

(defun search-files(query)
"Searches files by query"
	(let ((search-string "")(search-result nil))
		(dolist (part query)
			(format t " ~S~%" part)
			(setf search-string (concatenate 'string search-string (string-downcase  part) ".*"))
			(format t " ~S~%" search-string)
		)
		(setf search-string (concatenate 'string ".*" search-string))
		(format t "----Regexp ~S~%" search-string)
		(setf file-reg (regexp:regexp-compile search-string ))
		(dolist (existing-file *files*)
			(when
				(regexp:regexp-exec file-reg  (string-downcase  (getf existing-file :name)) :boolean t)
				(push (list :name (getf existing-file :name) :hash (getf existing-file :hash)) search-result)
			)
		)
		(princ search-result)
		search-result
	)
	;;(let((yes nil)(search-result nil))
	;;	(dolist (existing-file *files*)
	;;		(setf file-words (split-by-one-space (getf existing-file :name)))
	;;		(setf yes nil)
	;;		(dolist (search-word query)
	;;			(dolist (file-word file-words)
	;;				(when 
	;;					(string-equal file-word search-word)
	;;					(push t yes)
	;;				)
	;;			)
	;;		)
	;;		(when
	;;			(equal (length yes) (length query))
	;;			(push (list :name (getf existing-file :name) :hash (getf existing-file :hash)) search-result)
	;;		)
	;;	)
	;;	(princ search-result)
	;;	search-result
	;;)
)

(defun delete-client(id)
"Deletes client form client list"
	(princ *clients*)
	(format t "~%")
	(setf *clients* 
		(remove-if #'(lambda (x)(equal (getf x :id) id) ) *clients*)
	)
	(princ *clients*)
	(setf *files*
		(remove-if #'(lambda (x)(null (getf x :clients) ) ) 
			(setf *files* 
				;(mapcar #'(lambda (x) (remove-if #'(lambda (y)(equal (getf y :client) id) ) (getf x :clients))) *files*)
				(dolist (existing-file *files*)
					(setf existing-file (remove-if #'(lambda (x)(equal (getf x :client) id) ) (getf existing-file :clients)))
				)
			)
		)
	)
)

(defun add-files(files client-id)
"Adds files to files list"
	(let ((x t))
		(dolist (new-file files)
		        (setf x t)
			(dolist (existing-file *files*)
				(when 
					(equal (getf new-file :hash) (getf existing-file :hash))
					(format t "---- Adding client ~S to file ~S~%" client-id (getf new-file :name))
					(setf *files* 
						(remove-if #'(lambda (x)(equal (getf x :hash) (getf new-file :hash) )) *files*)
					)		
					(push (list :name (getf existing-file :name) :hash (getf existing-file :hash) :clients (cons (list :client client-id) (getf existing-file :clients))) *files*)
					(setf x nil)
				)
			)
			(when  (equal x t) 
				(format t "---- Adding file~S~%" (getf new-file :name))
				(push (list  :name (getf new-file :name) :hash (getf new-file :hash) :clients (list (list :client client-id))) *files*)
			)
		)
	)
)

(defun get-client-by-hash(file-hash client-id)
"Generates client list by file hash"
	(format t "---- Start scanning file hash ~S~%" file-hash)
	(let ((file-info (car (remove-if-not #'(lambda (z)(equal (getf z :hash) file-hash)) *files*))) 
		(download (car (remove-if-not #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))))
		(when 
			(not (null file-info))
			(format t "---- Download file ~S~%" (getf file-info :name) )
			(if 
				(null download)
				(push (list :id client-id :hash file-hash :clients (getf file-info :clients) :curent nil) *downloads*)
				(when 
					(not (equal (getf download :hash) file-hash))
					(setf *downloads* (remove-if #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))
					(push (list :id client-id :hash file-hash :clients (getf file-info :clients) :curent nil) *downloads*)
				)
			)
		)
		
	)
)

(defun get-next-client(client-id)
"Retrieves next client from list"
	(format t "---- Returning client ~S~%" client-id)
	(let ((download (car (remove-if-not #'(lambda (x)(equal (getf x :id) client-id)) *downloads*)))
		(res nil))
		(format t "---- Returning client ~S~%" download)
		(when 
			(not (null download))
			(format t "---- Found ~%")
			(setf *downloads* (remove-if #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))
			(setf res (pop (getf download :clients)))
			(format t "---- Res ~S~%" res)
			(push (list :id client-id :hash (getf download :hash) :clients (getf download :clients) :curent res) *downloads*)
		)
		(cond 
			( 	(null res)  
				(setf *downloads* (remove-if #'(lambda (x)(equal (getf x :id) client-id)) *downloads*))
				'E 
			)
			(t (cdr (cdr(car (remove-if-not #'(lambda (x)(equal (getf x :id) (getf res :client))) *clients*)))))
		)
	)
	
)

(defun split-by-one-space (string)
"Splits string by spaces"
	(loop for i = 0 then (1+ j)
		as j = (position #\Space string :start i)
		collect (subseq string i j)
		while j
	)
)

(defun init-variables()
"Initialization of variables"
	(setf count 0)
	(defparameter *files* nil)
	(setf *files* (load-db "files.sd"))
	(defparameter *clients* nil)
	(setf *clients* (load-db "clients.sd"))
	(defparameter *downloads* nil)
	(format t "---- Parameters inited~%")
)

(defun save-server-info()
	(format t "---- Save data to disk~%")
	(save-db *files* "files.sd")
	(save-db *clients* "clients.sd")
)


(defun save-db (param filename)
	(with-open-file (out filename
				:direction :output
				:if-exists :supersede)
		(with-standard-io-syntax
			(print param out)
		)
	)
)
            
(defun load-db (filename)
	(let ((data nil))
		(with-open-file (in filename :if-does-not-exist nil)
			(when (not (null in))
				(with-standard-io-syntax
					(setf data (read in))
				)
			)
		)
		data
	)
)